package com.tool.app.utils;

import com.tool.app.entity.Jedis;
import com.tool.app.entity.JedisCluster;
import lombok.Getter;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * redis工具类
 */
@Component
public class RedisUtils {


	@Getter
	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	@Getter
	@Resource
	private StringRedisTemplate stringRedisTemplate;


	private boolean isLettuceConnectio;

	public static final String lockStr = "lock:";
	public static final String lockLuaStr = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then  return redis.call('expire',KEYS[1],ARGV[2])  else return 0 end";

	//jedis 返回布尔值会变成Long类型 ,所以lua脚本返回布尔值或布尔字符串没有意义, 还需要解析
	public static final String lockLuaBol = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return true else return false end";
	public static final String unLockScriptStr = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
	public RedisScript<Boolean> lockScript = new DefaultRedisScript<>(lockLuaStr, Boolean.class);
	public RedisScript<Boolean> unLockScript = new DefaultRedisScript<>(unLockScriptStr, Boolean.class);

	/**
	 * 如果是jedis客户端, 集群模式执行lua脚本会报错
	 * EvalSha is not supported in cluster environment.
	 */
	@PostConstruct
	private void setConnectType() {
		if (stringRedisTemplate.getConnectionFactory() instanceof LettuceConnectionFactory) {
			this.isLettuceConnectio = false;
		} else {
			this.isLettuceConnectio = false;
		}
	}


	/**
	 * @param lockKey     唯一
	 * @param lockTimeOut 锁时间 单位秒
	 * @param arg         业务标识
	 * @return true/false
	 */
	public boolean lock(String lockKey, long lockTimeOut, String arg) {
		//return Boolean.TRUE.equals(stringRedisTemplate.execute(lockScript,Collections.singletonList(lockStr.concat(lockKey)), arg, String.valueOf(lockTimeOut)));
		Object result = stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
			// 获取连接
			Object nativeConnection = connection.getNativeConnection();
			// 集群模式的实例 (Jedis)
			if (nativeConnection instanceof JedisCluster) {
				return ((JedisCluster) nativeConnection).eval(lockLuaStr,
						Collections.singletonList(lockStr.concat(lockKey)), Arrays.asList(arg, String.valueOf(lockTimeOut)));
			}
			// 单机模式的实例 (Jedis)
			if (nativeConnection instanceof Jedis) {
				return ((Jedis) nativeConnection).eval(lockLuaStr, Collections.singletonList(lockStr.concat(lockKey)), Arrays.asList(arg, String.valueOf(lockTimeOut)));
			}
			//lettuce  lua脚本, 返回类型,key的数量, key,参数1,2,3
			return connection.eval(lockLuaStr.getBytes(StandardCharsets.UTF_8),
					ReturnType.INTEGER, 1,
					lockStr.concat(lockKey).getBytes(StandardCharsets.UTF_8),
					arg.getBytes(StandardCharsets.UTF_8),
					String.valueOf(lockTimeOut).getBytes(StandardCharsets.UTF_8));
		});
		return result != null && (Long) result == 1L;
	}

	/**
	 * @param lockKey 唯一
	 * @param arg     业务标识
	 * @return true/false
	 */
	public boolean unlock(String lockKey, String arg) {
		//return Boolean.TRUE.equals(stringRedisTemplate.<Boolean>execute(unLockScript, keys, arg));
		Object result = stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
			// 获取连接
			Object nativeConnection = connection.getNativeConnection();
			// 集群模式的实例 (Jedis)
			if (nativeConnection instanceof JedisCluster) {
				return  ((JedisCluster) nativeConnection).eval(lockLuaStr, Collections.singletonList(lockStr.concat(lockKey)), Collections.singletonList(arg));
			}
			// 单机模式的实例 (Jedis)
			if (nativeConnection instanceof Jedis) {
				return  ((Jedis) nativeConnection).eval(lockLuaStr, Collections.singletonList(lockStr.concat(lockKey)), Collections.singletonList(arg));
			}
			//lettuce
			return connection.eval(lockLuaStr.getBytes(StandardCharsets.UTF_8),
					ReturnType.INTEGER, 1,
					lockStr.concat(lockKey).getBytes(StandardCharsets.UTF_8),
					arg.getBytes(StandardCharsets.UTF_8));
		});
		return result != null && (Long) result == 1L;
	}


	/**
	 * lua脚本执行
	 * @param script  lua
	 * @param keys  key
	 * @param args  参数集合, 按lua脚本参数顺序添加
	 * @return
	 */
	public Object evalScript(String script, List<String> keys, List<String> args) {
		Object result = stringRedisTemplate.execute((RedisCallback<Object>) connection -> {
			// 获取连接
			Object nativeConnection = connection.getNativeConnection();
			// 集群模式的实例 (Jedis)
			if (nativeConnection instanceof JedisCluster) {
				return ((JedisCluster) nativeConnection).eval(script, keys, args);
			}
			// 单机模式的实例 (Jedis)
			if (nativeConnection instanceof Jedis) {
				return ((Jedis) nativeConnection).eval(script, keys, args);
			}
			//lettuce
			List<String> argList = new ArrayList<>(keys);
			argList.addAll(args);
			byte[][] array = argList.stream().map(s -> s.getBytes(StandardCharsets.UTF_8)).toArray(byte[][]::new);
			return connection.eval(script.getBytes(StandardCharsets.UTF_8),
					ReturnType.INTEGER, keys.size(), array);
		});
		return result;
	}


	public String get(String key) {
		return (String) redisTemplate.opsForValue().get(key);
	}

	public void set(String key, String value) {
		redisTemplate.opsForValue().set(key, value);
	}
}
